const std = @import("std");
const builtin = @import("builtin");
pub const config = @import("config");

pub const c = @cImport({
    @cInclude("signal.h");
    @cInclude("awtk.h");
    @cInclude("base/assets_manager.h");
    @cInclude("mvvm/mvvm.h");
});

pub const Emitter = c.emitter_t;
pub const Object = c.tk_object_t;
pub const ObjectVTable = c.object_vtable_t;
pub const Value = c.value_t;

const Allocator = std.mem.Allocator;

pub var gallocator: Allocator = undefined;
var mallocator: if (builtin.mode == .Debug or builtin.mode == .ReleaseSafe) MAllocator else void = undefined;

pub var glanguage: Language = .English;

// @typeName(type) : dirName.fileName.Type
// baseTypeName(type): Type
pub fn baseTypeName(Type: type) [:0]const u8 {
    const type_name = @typeName(Type);
    const p = std.mem.lastIndexOfScalar(u8, type_name, '.') orelse return type_name;

    return type_name[p + 1 ..];
}

pub const String = struct {
    array: std.ArrayList(u8) = undefined,

    pub fn init(num: usize) String {
        var string = String{
            .array = std.ArrayList(u8).initCapacity(gallocator, num) catch unreachable,
        };
        string.array.append(gallocator, 0) catch unreachable;

        return string;
    }

    pub fn initWithStr(s: []const u8) String {
        var string = String.init(32);
        string.setStr(s) catch unreachable;
        return string;
    }

    pub fn initWithEnumOptions(Enum: type) String {
        var string = String.init(32);
        inline for (@typeInfo(Enum).Enum.fields) |field| {
            string.append(field.name ++ ";") catch unreachable;
        }

        return string;
    }

    pub fn deinit(self: *String) void {
        self.array.deinit(gallocator);
    }

    pub fn format(self: String, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
        _ = options;
        if (comptime std.mem.eql(u8, fmt, "s")) {
            try std.fmt.format(writer, "{s}", .{self.array.items});
        } else {
            try std.fmt.format(writer, "len: {d}", .{self.array.items.len});
        }
    }

    pub fn setStr(self: *String, s: []const u8) !void {
        self.array.clearRetainingCapacity();
        try self.array.appendSlice(gallocator, s);
        try self.array.append(gallocator, 0); // C语言字符串以0结尾

        //std.log.debug("awtk.String: len: {d}, address: {*}", .{ self.array.items.len, self.array.items.ptr });
    }

    pub fn setCStr(self: *String, cs: [*c]const u8) !void {
        const s = cStrToSlice(cs) orelse "";
        try self.setStr(s);
    }

    pub fn append(self: *String, s: []const u8) !void {
        if (self.array.getLastOrNull()) |last| {
            if (last == 0) {
                try self.array.insertSlice(self.array.items.len - 1, s);
                try self.array.append(0);
                return;
            }
        }

        try self.array.appendSlice(s);
        try self.array.append(0);
    }

    pub fn cstr(self: String) [*c]const u8 {
        return self.array.items.ptr;
    }

    pub fn str(self: String) [:0]const u8 {
        var s: [:0]const u8 = undefined;
        s.len = self.len();
        s.ptr = @ptrCast(self.array.items.ptr);
        return s;
    }

    pub fn len(self: String) usize {
        const l = self.array.items.len;
        return if (l == 0) 0 else l - 1;
    }

    pub fn eql(self: String, s: []const u8) bool {
        return std.mem.eql(u8, self.str(), s);
    }
};

/// 转换zig的内存分配器为C的内存分配器
const MAllocator = struct {
    mem_allocator: c.mem_allocator_t,
    allocator: std.mem.Allocator,

    const Self = @This();
    const SpaceType = usize;
    const head_size: SpaceType = @sizeOf(SpaceType); // 存储内存块大小信息的空间的大小

    const vtable: c.mem_allocator_vtable_t = .{
        .alloc = alloc,
        .realloc = realloc,
        .free = free,
        .destroy = destroy,
        .dump = dump,
    };

    pub fn init(self: *Self, allocator: std.mem.Allocator) *c.mem_allocator_t {
        self.allocator = allocator;
        self.mem_allocator.vt = &vtable;
        return &self.mem_allocator;
    }

    /// zig的内存分配器在realloc及free时需要知晓内存块的大小，而C的内存管理并不能提供此信息
    /// 故分配内存时，在每块内存块前端多分配usize个字节用于存储其大小信息
    fn alloc(callocator: ?*c.mem_allocator_t, size: u32, func: ?[*:0]const u8, line: u32) callconv(.C) ?*anyopaque {
        _ = func;
        _ = line;
        const mem_allocator = callocator orelse return null;
        const space: SpaceType = size + head_size;
        const self: *Self = @ptrCast(mem_allocator);
        const slice = self.allocator.alloc(u8, @intCast(space)) catch return null;
        @memcpy(slice[0..head_size], std.mem.toBytes(space)[0..]); // 在内存块前端存储其大小信息
        return @ptrCast(&slice[head_size]); // 返回内存块的大小信息之后的内存地址
    }

    fn realloc(callocator: ?*c.mem_allocator_t, cptr: ?*anyopaque, size: u32, func: ?[*:0]const u8, line: u32) callconv(.c) ?*anyopaque {
        const mem_allocator = callocator orelse return null;
        const ptr = cptr orelse return alloc(callocator, size, func, line);
        const pdata: [*]u8 = @ptrCast(ptr);
        const phead = pdata - head_size; // 内存块的首地址
        const old_space = std.mem.bytesToValue(u32, phead[0..head_size]);
        const self: *Self = @ptrCast(mem_allocator);
        const space: SpaceType = size + head_size;
        const slice = self.allocator.realloc(phead[0..old_space], @intCast(space)) catch return null;

        @memcpy(slice[0..head_size], std.mem.toBytes(space)[0..]); // 更新内存块的大小信息
        return @ptrCast(&slice[head_size]);
    }

    fn free(callocator: ?*c.mem_allocator_t, cptr: ?*anyopaque) callconv(.C) void {
        const mem_allocator = callocator orelse return;
        const ptr = cptr orelse return;
        const pdata: [*]u8 = @ptrCast(ptr);
        const phead = pdata - head_size; // 内存块的首地址
        const old_size = std.mem.bytesToValue(u32, phead[0..head_size]);
        const self: *Self = @ptrCast(mem_allocator);
        self.allocator.free(phead[0..old_size]);
    }

    fn destroy(callocator: ?*c.mem_allocator_t) callconv(.C) c.ret_t {
        _ = callocator;
        return c.RET_OK;
    }

    fn dump(callocator: ?*c.mem_allocator_t) callconv(.C) c.ret_t {
        _ = callocator;
        return c.RET_OK;
    }
};

pub const Language = enum(u8) {
    English,
    Chinese,

    pub fn fromInt(iv: u8) !Language {
        if (iv > @intFromEnum(Language.Chinese)) {
            return error.InvalidEnumValue;
        }

        return @enumFromInt(iv);
    }

    pub fn apply(self: Language) !void {
        const locale: struct {
            language: [:0]const u8,
            country: [:0]const u8,
        } = switch (self) {
            .English => .{ .language = "en", .country = "US" },
            .Chinese => .{ .language = "zh", .country = "CN" },
        };

        const ret = c.locale_info_change(c.locale_info(), locale.language, locale.country);
        if (ret == c.RET_OK) {
            std.log.debug("language changed: {s}", .{@tagName(self)});
        } else {
            return error.LanguageChangeError;
        }
    }
};

pub fn tr(str: [:0]const u8) [:0]const u8 {
    const cstr = c.locale_info_tr(c.locale_info(), str);
    return cStrToSlice(cstr) orelse str;
}

pub fn getLanguage() Language {
    return glanguage;
}

pub fn setLanguage(new_language: Language) !void {
    if (new_language != glanguage) {
        try new_language.apply();
        glanguage = new_language;
    }
}

// a.b.c => a
pub fn frontName(path: []const u8) []const u8 {
    const idx = std.mem.indexOfScalar(u8, path, '.') orelse return path;
    return path[0..idx];
}

// a.b.c => b.c
pub fn subPath(path: []const u8) []const u8 {
    const idx = std.mem.indexOfScalar(u8, path, '.') orelse return "";
    return path[idx + 1 ..];
}

fn onSignal(sid: c_int) callconv(.c) void {
    std.log.warn("AWTK exit by signal: {d}", .{sid});
    quit();
}

fn assetsInit() !void {
    std.log.info("assets init", .{});
    const am = c.assets_manager();
    _ = c.assets_manager_set_theme(am, "default");
    _ = c.assets_manager_preload(am, c.ASSET_TYPE_STYLE, "default");
    const ret = c.tk_init_assets();
    if (ret != c.RET_OK) {
        return error.CFail;
    }
}

pub fn init(allocator: Allocator) !void {
    std.log.info("awtk init", .{});
    gallocator = allocator;
    if (@sizeOf(@TypeOf(mallocator)) > 0 and @hasField(c, "tk_mem_set_allocator")) {
        std.log.info("set zig allocator for awtk", .{});
        _ = c.tk_mem_set_allocator(mallocator.init(allocator));
    }

    _ = c.tk_pre_init();

    const w: c.wh_t = @intCast(config.width);
    const h: c.wh_t = @intCast(config.high);
    const ret = c.tk_init(w, h, c.APP_SIMULATOR, @ptrCast(config.app_name), null);
    if (ret != c.RET_OK) {
        return error.AwtkInitFailed;
    }

    _ = c.signal(c.SIGINT, onSignal);
    _ = c.signal(c.SIGTERM, onSignal);

    _ = c.font_manager_set_standard_font_size(c.font_manager(), false);
    _ = c.system_info_set_default_font(c.system_info(), "default");

    try assetsInit();
    _ = c.locale_info_reload(c.locale_info());
    _ = c.tk_ext_widgets_init();
    _ = c.log_set_log_level(c.LOG_LEVEL_DEBUG);
}

pub fn deinit() void {
    _ = c.tk_exit();
}

pub fn run() u8 {
    return @intCast(c.tk_run());
}

pub fn quit() void {
    _ = c.tk_quit();
}

pub const Notify = enum {
    NotImpl,
    Ok,
    Fail,
    PropertyChanged,
    ItemsChanged,

    pub fn toRet(self: Notify) c.ret_t {
        return switch (self) {
            .Ok => c.RET_OK,
            .Fail => c.RET_FAIL,
            .PropertyChanged => c.RET_OBJECT_CHANGED,
            .ItemsChanged => c.RET_ITEMS_CHANGED,
            else => c.RET_NOT_IMPL,
        };
    }
};

// C的指针可以指向单项或多项数据，而zig的单项指针和多项指针是明确区分的
// 故zig调用C时将C指针转换为[*c]T型指针，此类指针可转换为zig的单项指针或多项指针
// 此处自动将cstr由[*c]const u8字符串转换为?[*:0]const u8
pub fn cStrEql(cstr: ?[*:0]const u8, slice: []const u8, ignore_first_cast: bool) bool {
    const str = cstr orelse return false;

    var i: usize = 0;
    while (true) : (i += 1) {
        if (str[i] == 0 and slice.len == i) {
            break;
        }

        if (i >= slice.len) {
            return false;
        }

        var sc = slice[i];
        var cc = str[i];
        if (ignore_first_cast and i == 0) {
            sc = std.ascii.toLower(sc);
            cc = std.ascii.toLower(cc);
        }

        if (sc != cc) {
            return false;
        }
    }

    return true;
}

pub fn cStrToSlice(cstr: [*c]const u8) ?[:0]const u8 {
    const str = cstr orelse return null;
    return std.mem.sliceTo(str, 0);
}

pub fn notify(obj: *Object, event_type: u32) void {
    _ = c.emitter_dispatch_simple_event(c.EMITTER(obj), event_type);
}
